/*
 * Serial.cpp
 *
 *  Created on: Jan 14, 2014
 *      Author: ppks
 */

#include "Arduino.h"
#include "defines.h"
#include "Serial.h"

/******************************************************************************/
/* Local Variables Declaration                                                */
/******************************************************************************/
static uint8_t serialBufferRX[RX_BUFFER_SIZE][UART_NUMBER];
static uint8_t serialBufferTX[TX_BUFFER_SIZE][UART_NUMBER];
static volatile uint8_t serialHeadRX[UART_NUMBER];
static volatile uint8_t serialTailRX[UART_NUMBER];
static volatile uint8_t serialHeadTX[UART_NUMBER];
static volatile uint8_t serialTailTX[UART_NUMBER];

/******************************************************************************/
/* Local Functions Declaration                                                */
/******************************************************************************/
void CommSerial_StoreDataInBuffer(uint8_t data, uint8_t portnum);


/******************************************************************************/
/* Function Code                                                              */
/******************************************************************************/

/*******************************************************************************
* Function Name: Comm_SerialOpen()
********************************************************************************
* Summary:
*
*
* Parameters:
*  None
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_Open(uint8_t port, uint32_t baud)
{
    uint8_t h = ((F_CPU  / 4 / baud -1) / 2) >> 8;
    uint8_t l = ((F_CPU  / 4 / baud -1) / 2);
    switch (port)
    {
    #if defined(PROMINI)
        case 0:
            UCSR0A  = (1<<U2X0);
            UBRR0H = h;
            UBRR0L = l;
            UCSR0B |= (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
            break;
    #endif
    #if defined(PROMICRO)
        #if (ARDUINO >= 100) && !defined(TEENSY20)
            case 0:
                /* disable the USB frame interrupt of arduino (it causes strong jitter and we dont need it) */
                UDIEN &= ~(1<<SOFE);
                break;
        #endif
        case 1:
            UCSR1A  = (1<<U2X1);
            UBRR1H = h;
            UBRR1L = l;
            UCSR1B |= (1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1);
            break;
    #endif
    #if defined(MEGA)
        case 0:
            UCSR0A  = (1<<U2X0);
            UBRR0H = h;
            UBRR0L = l;
            UCSR0B |= (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
            break;
        case 1:
            UCSR1A  = (1<<U2X1);
            UBRR1H = h;
            UBRR1L = l;
            UCSR1B |= (1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1);
            break;
        case 2:
            UCSR2A  = (1<<U2X2);
            UBRR2H = h;
            UBRR2L = l;
            UCSR2B |= (1<<RXEN2)|(1<<TXEN2)|(1<<RXCIE2);
            break;
        case 3:
            UCSR3A  = (1<<U2X3);
            UBRR3H = h;
            UBRR3L = l;
            UCSR3B |= (1<<RXEN3)|(1<<TXEN3)|(1<<RXCIE3);
            break;
    #endif
    }
}

/*******************************************************************************
* Function Name: Comm_SerialClose()
********************************************************************************
* Summary:
*
*
* Parameters:
*  None
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_Close(uint8_t port)
{
    switch (port)
    {
    #if defined(PROMINI)
        case 0:
            UCSR0B &= ~((1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<UDRIE0));
            break;
    #endif
    #if defined(PROMICRO)
        case 1:
            UCSR1B &= ~((1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1)|(1<<UDRIE1));
            break;
    #endif
    #if defined(MEGA)
        case 0:
            UCSR0B &= ~((1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0)|(1<<UDRIE0));
            break;
        case 1:
            UCSR1B &= ~((1<<RXEN1)|(1<<TXEN1)|(1<<RXCIE1)|(1<<UDRIE1));
            break;
        case 2:
            UCSR2B &= ~((1<<RXEN2)|(1<<TXEN2)|(1<<RXCIE2)|(1<<UDRIE2));
            break;
        case 3:
            UCSR3B &= ~((1<<RXEN3)|(1<<TXEN3)|(1<<RXCIE3)|(1<<UDRIE3));
            break;
    #endif
    }
}

/*******************************************************************************
* Function Name: Interrupt Driven UART Transmitter Implementation
********************************************************************************
* Summary:
*
* Parameters:
*  None
*
* Return:
*  None
*
*******************************************************************************/
#if defined(PROMINI) || defined(MEGA)
/*******************************************************************************
* Function Name: ISR Handler for UART1 (on MEGA or PROMINI)
*******************************************************************************/
#if defined(PROMINI)
ISR(USART_UDRE_vect)  /* Serial 0 on a PROMINI */
#elif defined(MEGA)
ISR(USART0_UDRE_vect) /* Serial 0 on a MEGA */
#endif
{
    uint8_t t = serialTailTX[0];

    if (serialHeadTX[0] != t)
    {
        if (++t >= TX_BUFFER_SIZE)
        {
            t = 0;
        }
        /* Transmit next byte in the ring */
        UDR0 = serialBufferTX[t][0];
        serialTailTX[0] = t;
    }

    /* Check if all data is transmitted . if yes disable transmitter UDRE interrupt */
    if (t == serialHeadTX[0])
    {
        UCSR0B &= ~(1<<UDRIE0);
    }
}
#endif /* defined(PROMINI) || defined(MEGA) */
#if defined(MEGA) || defined(PROMICRO)
/*******************************************************************************
* Function Name: ISR Handler for UART1 (on MEGA or PROMICRO)
*******************************************************************************/
ISR(USART1_UDRE_vect)
{
    uint8_t t = serialTailTX[1];
    if (serialHeadTX[1] != t)
    {
        if (++t >= TX_BUFFER_SIZE)
        {
            t = 0;
        }
        /* Transmit next byte in the ring */
        UDR1 = serialBufferTX[t][1];
        serialTailTX[1] = t;
    }

    /* Check if all data is transmitted . if yes disable transmitter UDRE interrupt */
    if (t == serialHeadTX[1])
    {
        UCSR1B &= ~(1<<UDRIE1);
    }
}
#endif /* defined(MEGA) || defined(PROMICRO) */
#if defined(MEGA)
/*******************************************************************************
* Function Name: ISR Handler for UART2 (on MEGA)
*******************************************************************************/
ISR(USART2_UDRE_vect)
{
    uint8_t t = serialTailTX[2];
    if (serialHeadTX[2] != t)
    {
        if (++t >= TX_BUFFER_SIZE)
        {
            t = 0;
        }
        /* Transmit next byte in the ring */
        UDR2 = serialBufferTX[t][2];
        serialTailTX[2] = t;
    }

    /* Check if all data is transmitted . if yes disable transmitter UDRE interrupt */
    if (t == serialHeadTX[2])
    {
        UCSR2B &= ~(1<<UDRIE2);
    }
}

/*******************************************************************************
* Function Name: ISR Handler for UART3 (on MEGA)
*******************************************************************************/
ISR(USART3_UDRE_vect)
{
    uint8_t t = serialTailTX[3];
    if (serialHeadTX[3] != t)
    {
        if (++t >= TX_BUFFER_SIZE)
        {
            t = 0;
        }
        /* Transmit next byte in the ring */
        UDR3 = serialBufferTX[t][3];
        serialTailTX[3] = t;
    }

    /* Check if all data is transmitted . if yes disable transmitter UDRE interrupt */
    if (t == serialHeadTX[3])
    {
        UCSR3B &= ~(1<<UDRIE3);
    }
}
#endif /* defined(MEGA) */

/*******************************************************************************
* Function Name: CommSerial_SendByte
********************************************************************************
* Summary:
*  Enables Interrupt for the transmitter on the specified port. The actual data
*  sending is executed in the interrupt handler
*
* Parameters:
*  uint8_t port - UART port number to use for data transmission
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_SendData(uint8_t port)
{
#if defined(PROMINI)
    UCSR0B |= (1<<UDRIE0);
#elif defined(PROMICRO)
    switch (port)
    {
        case 0:
            while(serialHeadTX[0] != serialTailTX[0])
            {
                if (++serialTailTX[0] >= TX_BUFFER_SIZE)
                {
                    serialTailTX[0] = 0;
                }
            #if !defined(TEENSY20)
                USB_Send(USB_CDC_TX,serialBufferTX[serialTailTX[0]],1);
            #else
                Serial.write(serialBufferTX[serialTailTX[0]],1);
            #endif
            }
            break;
        case 1:
            UCSR1B |= (1<<UDRIE1);
            break;
    }
#elif defined(MEGA)
    switch (port)
    {
        case 0:
        {
            UCSR0B |= (1<<UDRIE0);
            break;
        }
        case 1:
        {
            UCSR1B |= (1<<UDRIE1);
            break;
        }
        case 2:
        {
            UCSR2B |= (1<<UDRIE2);
            break;
        }
        case 3:
        {
            UCSR3B |= (1<<UDRIE3);
            break;
        }
    }
#endif /* defined(MEGA) */
}

/*******************************************************************************
* Function Name: CommSerial_StoreDataInBuffer()
********************************************************************************
* Summary:
*  Stores 1 byte of the data in the ring buffer for specified UART index
*
* Parameters:
*  None
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_StoreDataInBuffer(uint8_t data, uint8_t portnum)
{
    uint8_t h = serialHeadRX[portnum];

    serialBufferRX[h++][portnum] = data;

    if (h >= RX_BUFFER_SIZE)
    {
        h = 0;
    }

    /* Update the new value of the head index into the buffer */
    serialHeadRX[portnum] = h;
}


/*******************************************************************************
* Function Name: Interrupt Driven UART Receiver Implementation
********************************************************************************
* Summary:
*
* Parameters:
*  None
*
* Return:
*  None
*
*******************************************************************************/
#if defined(PROMINI)
/*******************************************************************************
* Function Name: ISR Handler for UART0 (on PROMINI)
*******************************************************************************/
ISR(USART_RX_vect)
{
    CommSerial_StoreDataInBuffer(UDR0, 0);
}
#elif defined(PROMICRO)
/*******************************************************************************
* Function Name: ISR Handler for UART0 (on PROMICRO)
*******************************************************************************/
ISR(USART1_RX_vect)
{
    CommSerial_StoreDataInBuffer(UDR1, 1);
}
#elif defined(MEGA)
/*******************************************************************************
* Function Name: ISR Handler for UART0 (on MEGA)
*******************************************************************************/
ISR(USART0_RX_vect)
{
    CommSerial_StoreDataInBuffer(UDR0, 0);
}

/*******************************************************************************
* Function Name: ISR Handler for UART1 (on MEGA)
*******************************************************************************/
ISR(USART1_RX_vect)
{
    CommSerial_StoreDataInBuffer(UDR1, 1);
}

/*******************************************************************************
* Function Name: ISR Handler for UART2 (on MEGA)
*******************************************************************************/
ISR(USART2_RX_vect)
{
    CommSerial_StoreDataInBuffer(UDR2, 2);
}

/*******************************************************************************
* Function Name: ISR Handler for UART3 (on MEGA)
*******************************************************************************/
ISR(USART3_RX_vect)
{
    CommSerial_StoreDataInBuffer(UDR3, 3);
}
#endif /* defined(MEGA) */

/*******************************************************************************
* Function Name: CommSerial_ReadByteFromBuffer()
********************************************************************************
* Summary:
* Reads one byte of data from the selcted UART ring buffer and adjust tail index
* for this buffer
*
* Parameters:
*  uint8_t port - UART port number from which buffer we will read the data
*
* Return:
*  uint8_t - 1 byte of data read from tail index on specified UART port buffer
*
*******************************************************************************/
uint8_t CommSerial_ReadByteFromBuffer(uint8_t port)
{
#if defined(PROMICRO)
    #if defined(TEENSY20)
        if (port == 0)
        {
            return Serial.read();
        }
    #else
        #if (ARDUINO >= 100)
            if (port == 0)
            {
                USB_Flush(USB_CDC_TX);
            }
        #endif
            if (port == 0)
            {
                return USB_Recv(USB_CDC_RX);
            }
    #endif
#endif /* defined(PROMICRO) */

    uint8_t t = serialTailRX[port];
    uint8_t c = serialBufferRX[t][port];

    if (serialHeadRX[port] != t)
    {
        if (++t >= RX_BUFFER_SIZE)
        {
            t = 0;
        }
        serialTailRX[port] = t;
    }

    return c;
}

/*******************************************************************************
* Function Name: CommSerial_IsNewDataAvailable
********************************************************************************
* Summary:
*  Checks whether the new data was received by UART and returns the size of the
*  data.
*
* Parameters:
*  uint8_t port - UART port index
*
* Return:
*  uint8_t - size in bytes of new available data (0 means no new data)
*
*******************************************************************************/
uint8_t CommSerial_IsNewDataAvailable(uint8_t port)
{
#if defined(PROMICRO)
#if !defined(TEENSY20)
    if(port == 0)
    {
        return USB_Available(USB_CDC_RX);
    }
#else
    if(port == 0)
    {
        return T_USB_Available();
    }
#endif /* !defined(TEENSY20) */
#endif /* defined(PROMICRO) */
    /* Return the difference between head and tail index in the circular buffer */
    return ((uint8_t)(serialHeadRX[port] - serialTailRX[port])) % RX_BUFFER_SIZE;
}

/*******************************************************************************
* Function Name: CommSerial_GetTXBuffSizeUsed
********************************************************************************
* Summary:
*  Checks and returns how many bytes are currently used in the TX buffer for
*  specified UART port index
*
* Parameters:
*  uint8_t port - UART port index
*
* Return:
*  uint8_t - size in bytes of used TX buffer
*
*******************************************************************************/
uint8_t CommSerial_GetTXBuffSizeUsed(uint8_t port)
{
    return ((uint8_t)(serialHeadTX[port] - serialTailTX[port])) % TX_BUFFER_SIZE;
}

/*******************************************************************************
* Function Name: CommSerial_IsTXCompleted
********************************************************************************
* Summary:
*
*
* Parameters:
*  uint8_t port - UART port index
*
* Return:
*  None
*
*******************************************************************************/
bool CommSerial_IsTXCompleted(uint8_t port)
{
	return (serialHeadTX[port] == serialTailTX[port]);
}

/*******************************************************************************
* Function Name: CommSerial_WriteByteToBuffer
********************************************************************************
* Summary:
*  Writes uint8 value to the TX buffer and increments the head index
*
* Parameters:
*  uint8_t port - UART port index
*  uint8_t c - input byte data to be written to the TX buffer
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_WriteByteToBuffer(uint8_t port,uint8_t c)
{
    uint8_t t = serialHeadTX[port];

    if (++t >= TX_BUFFER_SIZE)
    {
        t = 0;
    }

    /* Append data to the TX buffer and increment head index */
    serialBufferTX[t][port] = c;
    serialHeadTX[port] = t;
}

/*******************************************************************************
* Function Name: CommSerial_WriteByteToBuffer
********************************************************************************
* Summary:
*  Writes uint8 value to the TX buffer and increments the head index
*
* Parameters:
*  uint8_t port - UART port index
*  uint8_t c - input byte data to be written to the TX buffer
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_WriteBlockToBuffer(uint8_t port, uint8_t *data, uint8_t size)
{
    uint8_t t = serialHeadTX[port];
    uint8_t byteCounter;

    /* Check if data size is less than the available space in the buffer */
    if (size > TX_BUFFER_SIZE - CommSerial_GetTXBuffSizeUsed(port))
    {
        /* Just return as we don't have enough space */
        /* TBD - add proper buffer handling */
        return;
    }

    for (byteCounter = 0; byteCounter < size; byteCounter++)
    {
        if (++t >= TX_BUFFER_SIZE)
        {
            t = 0;
        }

        /* Append data to the TX buffer and increment head index */
        serialBufferTX[t][port] = data[byteCounter];
        serialHeadTX[port] = data[byteCounter];
    }
}

/*******************************************************************************
* Function Name: CommSerial_SendUint8
********************************************************************************
* Summary:
*  Sends uint8 data via specified UART port
*
* Parameters:
*  uint8_t port - UART port index
*  uint8_t c - input data to be written to the TX buffer
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_SendUint8(uint8_t port,uint8_t c)
{
    /* Append the data to the TX buffer for specified UART port */
    CommSerial_WriteByteToBuffer(port, c);

    /* Enable Interrupt based transmitter */
    CommSerial_SendData(port);
}

/*******************************************************************************
* Function Name: CommSerial_SendUint16
********************************************************************************
* Summary:
*  Sends uint16 data via specified UART port
*
* Parameters:
*  uint8_t port - UART port index
*  uint16_t c - input data to be written to the TX buffer
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_SendUint16(uint8_t port,uint16_t c)
{
    /* Append the data to the TX buffer for specified UART port */
    CommSerial_WriteByteToBuffer(port, (c    ) & 0xFF);
    CommSerial_WriteByteToBuffer(port, (c>> 8) & 0xFF);

    /* Enable Interrupt based transmitter */
    CommSerial_SendData(port);
}

/*******************************************************************************
* Function Name: CommSerial_SendUint32
********************************************************************************
* Summary:
*  Sends uint32 data via specified UART port
*
* Parameters:
*  uint8_t port - UART port index
*  uint32_t c - input data to be written to the TX buffer
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_SendUint32(uint8_t port,uint32_t c)
{
    /* Append the data to the TX buffer for specified UART port */
    CommSerial_WriteByteToBuffer(port, (c    ) & 0xFF);
    CommSerial_WriteByteToBuffer(port, (c>> 8) & 0xFF);
    CommSerial_WriteByteToBuffer(port, (c>>16) & 0xFF);
    CommSerial_WriteByteToBuffer(port, (c>>24) & 0xFF);

    /* Enable Interrupt based transmitter */
    CommSerial_SendData(port);
}

/*******************************************************************************
* Function Name: CommSerial_WriteCRLF
********************************************************************************
* Summary:
*  Write CRLF flags to the specified UART port
*
* Parameters:
*  uint8_t port - UART port index
*
* Return:
*  None
*
*******************************************************************************/
void CommSerial_WriteCRLF(uint8_t port)
{
    /* Write Data into the ring buffer */
    CommSerial_WriteByteToBuffer(port, 0x0D);
    CommSerial_WriteByteToBuffer(port, 0x0A);

    /* Enable Interrupt based transmitter */
    CommSerial_SendData(port);
}
